import { isSameVnode } from "./index";

export function patch ( oldVnode, vnode ) {
  if ( oldVnode.nodeType === 1 ) {
    // 首次渲染
    const parentElm = oldVnode.parentNode; // 获取父元素
    const elm = createElm( vnode );
    // vue2为什么不能挂载到html,body上？

    // 1、先插入新的节点，在老的之后
    parentElm.insertBefore( elm, oldVnode.nextSibling );
    // 2、在删除老的节点
    parentElm.removeChild( oldVnode );
    // 因为body,html是根节点，且只能有一个，如果删除了根节点页面将的空白

    return elm;
  } else {
    patchVnoode( oldVnode, vnode ); // 比较两个虚拟节点的差异
    // 更新
    return vnode.el; // 最终返回新的el元素(真实的节点)
  }
}

/**
 * 
 * 新老虚拟节点对比策略
 * 只会对比同层级
 * 1、如果节点相同会进行复用，继续对比属性和子元素
 * 2、如果节点不相同直接直接用新的替换老的
 * 
 */
function patchVnoode ( oldVnode, vnode ) {
  // 节点不一样，新元素直接替换老元素
  if ( !isSameVnode( oldVnode, vnode ) ) {
    return oldVnode.el.parentNode.replaceChild( createElm( vnode ), oldVnode.el );
  }

  // 新老虚拟节点tag,key相同，复用老的真实dom
  let el = ( vnode.el = oldVnode.el );
  if ( !oldVnode.tag ) {
    // 不是标签是文本，且内容不一样
    if ( oldVnode.text !== vnode.text ) {
      // 直接更新文本
      return ( oldVnode.el.textContent = vnode.text );
    }
  }
  // 对比Props、Style
  updateProperties( vnode, oldVnode.props );

  let oldChildren = oldVnode.children || [];
  let newChildren = vnode.children || [];
  // 1、如果新老都是子元素diff对比
  // 2、如果新的有子元素，老的没有，直接新增
  // 3、如果新的没有子元素，老的有，直接删除
  if ( oldChildren.length > 0 && newChildren.length > 0 ) {
    // 两方都有儿子
    updateChildren( el, oldChildren, newChildren );
  } else if ( newChildren.length > 0 ) {
    // 新的有子元素，老的没有，直接新增
    newChildren.forEach( ( child ) => el.appendChild( createElm( child ) ) );
  } else if ( oldChildren.length > 0 ) {
    // 新的没有子元素，老的有，直接删除
    el.innerHTML = "";
  }

}

// 新老子虚拟节点对比
function updateChildren ( el, oldChildren, newChildren ) {
  let oldStartIndex = 0;
  let newStartIndex = 0;
  let oldEndIndex = oldChildren.length - 1;
  let newEndIndex = newChildren.length - 1;
  let oldStartVnode = oldChildren[ oldStartIndex ];
  let newStartVnode = newChildren[ newStartIndex ];
  let oldEndVnode = oldChildren[ oldEndIndex ];
  let newEndVnode = newChildren[ newEndIndex ];
  function mappings () {
    let childrenMap = {}
    oldChildren.forEach( ( vNode ) => {
      childrenMap[ vNode.key ] = vNode
    } )
    return childrenMap
  }
  let childrenMap = mappings();
  // 如果其中有任何一个起始索引超出结束索引的位置，证明有一方已经对比完毕，停止循环
  while ( newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex ) {
    if ( !oldStartVnode ) {
      // 乱序对比老的已经复用过
      oldStartVnode = oldChildren[ ++oldStartIndex ];
    } else if ( !oldEndVnode ) {
      // 乱序对比老的已经复用过
      oldEndVnode = oldChildren[ --oldEndIndex ];
    } else if ( isSameVnode( oldStartVnode, newStartVnode ) ) {   // 头头对比
      patchVnoode( oldStartVnode, newStartVnode )
      oldStartVnode = oldChildren[ ++oldStartIndex ];
      newStartVnode = newChildren[ ++newStartIndex ];
    } else if ( isSameVnode( oldEndVnode, newEndVnode ) ) { // 尾尾对比
      patchVnoode( oldEndVnode, newEndVnode )
      oldEndVnode = oldChildren[ --oldEndIndex ];
      newEndVnode = newChildren[ --newEndIndex ];
    } else if ( isSameVnode( oldStartVnode, newEndVnode ) ) { // 老头新尾对比
      patchVnoode( oldStartVnode, newEndVnode )
      // 头变尾
      // inseetBefore是具备移动性的，移动走了，原来的就不存在了
      el.insertBefore( oldStartVnode.el, oldEndVnode.el.nextSibling );
      oldStartVnode = oldChildren[ ++oldStartIndex ];
      newEndVnode = newChildren[ --newEndIndex ];
    } else if ( isSameVnode( oldEndVnode, newStartVnode ) ) { // 老尾新头对比
      patchVnoode( oldStartVnode, newEndVnode )
      el.insertBefore( oldEndVnode.el, oldStartVnode.el );
      // 尾变头
      oldEndVnode = oldChildren[ --oldEndIndex ];
      newStartVnode = newChildren[ ++newStartIndex ];
    } else {
      // 头头、尾尾、头尾、尾头都不相等，可能在中间位置
      let oldVnode = childrenMap[ newStartVnode.key ]
      if ( !oldVnode ) {
        // 如果没有能够复用的节点，直接新增插入在当前老节点起始位置之前
        el.insertBefore( createElm( newStartVnode ), oldStartVnode.el );
      } else {
        // 复用老的节点
        patchVnoode( oldVnode, newStartVnode );
        el.insertBefore( oldVnode.el, oldStartVnode.el );
        oldChildren[ oldStartIndex ] = null // 已经复用过下次直接跳过
      }
      newStartVnode = newChildren[ ++newStartIndex ];
    }
  }
  // 老的虚拟dom未对比完，需要全部删除
  for ( let index = oldStartIndex; index <= oldEndIndex; index++ ) {
    const oldVnode = oldChildren[ index ];
    if ( oldVnode ) {
      el.removeChild( oldVnode.el )
    }
  }

  // 新的虚拟dom未对比完，需要全部插入
  for ( let index = newStartIndex; index <= newEndIndex; index++ ) {
    const newVnode = newChildren[ index ];
    // 如果是头头对比直接追加在最后面
    // 如果是尾尾对比，需要插入之前

    // 当前位置的下一个节点
    let anchor = newChildren[ newEndIndex + 1 ] == null ? null : newChildren[ newEndIndex + 1 ].el;
    // 如果当前位置的下一个节点存在，之前插入下一个节点之前
    // 如果不存在直接追加在最后面
    el.insertBefore( createElm( newVnode ), anchor )
  }
}

// Props、Style处理
function updateProperties ( vnode, oldProps = {} ) {
  // oldProps 可能不存在，如果存在就表示更新
  let newProps = vnode.props || {};
  let el = vnode.el;

  let oldStyle = oldProps.style || {};
  let newStyle = newProps.style || {};

  // 处理Style如果是更新操作，老的有，新的没有，直接删除
  for ( let key in oldStyle ) {
    if ( !( key in newStyle ) ) {
      el.style[ key ] = "";
    }
  }

  // 处理Props如果是更新操作，老的有，新的没有，直接删除
  for ( let key in oldProps ) {
    if ( !( key in newProps ) ) {
      el.removeAttribute( key );
    }
  }

  // 新的直接覆盖老的Props、Style
  for ( let key in newProps ) {
    if ( key == "style" ) {
      for ( let styleName in newStyle ) {
        el.style[ styleName ] = newStyle[ styleName ]; // 对样式的特殊处理
      }
    } else {
      el.setAttribute( key, newProps[ key ] );
    }
  }
}


// 根据虚拟节点产生真实节点
export function createElm ( vnode ) {
  const { tag, props, children, text } = vnode;
  if ( typeof tag == "string" ) {
    vnode.el = document.createElement( tag ); // 把创建的真实dom和虚拟dom映射在一起方便后续更新和复用
    // 处理属性
    updateProperties( vnode );
    // 把子元素添加到父容器中
    children.forEach( ( child ) => {
      vnode.el.appendChild( createElm( child ) );
    } );
  } else {
    vnode.el = document.createTextNode( text );
  }
  return vnode.el;
}